這裡是「Three.js學習日誌」的第29篇,這篇主要在講解如何把Blender建立的模型,拿到three.js專案裡面使用。這系列的文章假設讀者看得懂javascript,並且有Canvas 2D Context的相關知識。
今天就是最後一戰了! 我們今天主要的目標就是利用Blender
去實作一個甜甜圈的建模,然後把它放到three.js
的scene
裡面進行渲染。
我們在上一回有提到,blender有非常多的預設快捷鍵,這邊我們先來講講比較常用的幾個:
就跟three.js
的orbitControls
差不多,只要按下中鍵就可以旋轉視角。
如果同時按住shift,則可以平移視角。
之所以是g,是因為它代表的是Grab(抓取),我們可以在物體/編輯(點/線/面)模式對個別對象做抓取,
而被抓取起來的物件就可以自由移動。
另外:
g
之後馬上接著按下x/y/z
,就可以鎖定在指定的軸向移動。g
之後馬上接著按下x/y/z
,在接著指定一個數字N
,就可以朝該軸向移動N
單位。g
之後馬上接著按下x/y/z
,然後接著在維持按住shift
的狀況下在按一次x/y/z
,就會可以限制在某個平面上移動(例如先按X
再按下y
,就會在xy
平面上移動)這邊要注意一下,blender跟three.js的坐標軸不太一樣,blender朝上的座標軸是Z,但three.js是y
旋轉的快捷鍵沒有甚麼意外的是r
,它跟抓取(g)
一樣,也可以在按下r
之後,按下指定的軸向,這樣就可以限制在以該軸為軸心做旋轉。
縮放的快捷鍵同樣也沒有甚麼意外的是s
,跟抓取(g)
一樣,也可以在按下s
之後,按下指定的軸向,這樣就可以限制在以該軸為軸向做拉伸、縮短。
我們在上一回其實有講過這個快捷鍵,這邊就不多提。
我們在上一回其實有講過這個快捷鍵,這邊也是不多提。
初次使用blender的新手通常會因為想要刪除物體,但又不知道怎麼刪除,覺得很苦惱,因為blender的刪除不是delete,而是x。
這邊說的新手就是我。。。
只要先選取物體/點/線/面然後按下x
,就會彈出小選單問你說是不是確定要刪除。
其實這個快捷鍵也可以直接點選上面的按鈕,點選之後就會出現新增物件的選單。
全選會把整個Scene裡面的物件都選起來,其中包括光源、甚至是攝影機。
數字鍵盤(Numpad)的用途是用來檢視上下左右前後、正交、主攝影機,...etc.所看到的景象。
如果你的電腦沒有Numpad,blender有提供一個設置可以打開,打開之後你就可以用鍵盤英文字母上方的數字模擬Numpad的行為(如上方右圖)。
按下(shift+a)然後從mesh這個類別裡面找到Torus
基本上所有的物體(Object),在被創建的時候,左下角都會出現這個視窗。
而且重點是,這個左下角的視窗,只要在當你一取消對該物體的Focus(Active),就會直接消失。
這時我們可以按下F9來讓他重新顯示(mac鍵盤的話要搭配fn(fn+f9))。
不過,如果你除了取消Focus(Active)之外,還對這個物體作了移動/旋轉/縮放,那就算是按下F9也沒辦法把它叫回來了。
就算是Ctrl+z也沒有用哦~
這個小視窗主要是讓我們決定這個物體的初始Config設置,就有點像three.js
torusGeometry
的建構式,它可以決定torus
的內、外圈、截面半徑,也可以決定物體的segments
, (也就是我們在three.js
講過的網格細分數量)
網格數量很重要~所以下好物件離手之前都要再三確認過設置沒問題
這邊segments
注意先不要下的太大,因為網格面數太多會導致模型的檔案容量過大。
建議可以看一下Blender guru的設置,在ep2 的 4:54
這邊我們需要調整一下大小,讓Torus比較接近現實生活中甜甜圈的大小。
畢竟如果按照我們現在設置的大小,這會是個怪物等級的甜甜圈XD,放到
three.js
的Scene裡面會超級大~
這邊我們把它縮小到大約直徑為10公分左右(按s)。
這邊接著要注意,在blender裡面,如果有做過任何的形變,最後一定要記得「Apply」。
所謂的「Apply」就是把擴張/縮小的值實際的附加到Geometry
頂點座標上。
按下Ctrl+A來Apply剛剛的形變(transform)。
這邊我們要點選右鍵,並使用一個黑魔法!! 「Smooth shade」直接把Torus的陰影修成不會這樣一格一格的。
其實這是某種shader的應用方式,原理是透過對每個面的normal
值做線性映射來計算曲面的陰影顏色,就有點像是我們之前說過的normal map(法線貼圖),是一種障眼法。
在這次的賽程我自己覺得時間太趕了,而且three.js東西真的太多,所以除了開場的webgl以外,沒有怎麼帶到shader的內容QQ,畢竟那算是更進階的know-how了。
接著,因為目前雖然Torus表面的陰影看起來已經優化,但我們若從Torus的邊緣看過去,仍然可以看到邊緣的亮橘色框線有一格一格的感覺。
所以我們這邊要來使用blender的modiefier(修改器)。
右邊側選單有一個扳手的小圖示~
假設我們把物體剛生成出來,並設定好參數(寬、高、網格細分等)這個階段稱作「先天」,那麼modiefier(修改器)則可以看成是一種「後天」對物體網格進行操作的手段。
這邊我們要使用的是Subdivision Surface這一種modiefier(修改器),它的用途是可以「後天」的為物體做進階的網格細分。
這邊如果在修改器裡面登打數字時,會莫名的出現「按一下結果出現兩個字」的狀況的話,可以切換一下輸入語言,換成英文
這邊順帶一提,假如你在按下Tab這邊切換回去編輯模式,你會發現網格面數好像並沒有增加(但是面確實變平整了),那是因為modiefier(修改器)其實也類似形變(transform),它也必須要經過「Apply」才能夠附加到Geometry
的頂點座標上面。
這邊如果要執行「Apply」的話,必須要先切換回去物體模式(Object mode),然後點modiefier右上角的小箭頭。
不過我們這邊先不做「Apply」,畢竟之後還有可能會再次調整這個細分的數值。
接下來我們要稍微的調整這個Torus網格的形狀,把它變得更像現實中的甜甜圈。
先切換到點編輯模式。
然後點亮這個按鈕。
這個按鈕的功能是比例調整(Propotional Editing),意思就是說當我們拖動其中一個頂點的時候,周遭的頂點也會根據與被拖動頂點距離的遠近,來產生線性的變化。
記得先按下g來拖動其中一個頂點。
這邊我們可以使用滑鼠滾輪來決定對周遭頂點影響的範圍大小(因為我們剛剛有調整過Scale,所以這邊可能得多滾幾下才可以看到調整影響範圍的提示圈圈)
這邊如果想要看更Detail的操作,可以看ep2的18:50
這邊我們要來開始製作甜甜圈上層的糖衣(Icing)。
原理是透過把
Geometry
的上半部複製出來一份,並且覆蓋在原本的Geometry
上面
首先使用Numpad的1,把視角轉到正視圖,接著開啟X-Ray模式,並且框選出上半部的Geometry
。
之所以開啟X-Ray模式是因為不開的話選不到背面的vertices
接下來按下(shift+d)來複製這些框選起來的網格,這邊要注意,複製 vertice並不會直接為我們創造一個新的物件。(blender反而會認為這些複製出來的頂點仍然屬於原本物件)
所以我們這邊在做完複製之後,還必須要按下p
,並選擇Selection
。
這邊我們可以看到我們選擇的上半部頂點群被獨立出來成為了Torus.001
這邊如果想看細節操作,可以看ep3的3:49
這邊因為我們剛複製出來的糖衣只是一層薄薄的Geometry
,然而真實的甜甜圈糖衣看起來應該更要有一種厚度感,所以我們在這邊要給糖衣套上另外一種修改器「Solidify(實體化)」,這是一種可以讓geometry
的每個頂點,沿著法向量向外延伸,進而增加模型厚度的一種修改器。
這邊可以注意到,透過複製Torus本體而創造出來的糖衣同樣會繼承本體的Subdivision修改器。
這邊我們可調整Subdivision修改器和Solidify修改器的順序,讓糖衣變成「先增厚再細分」。
這邊的調整修改器所造成的差異主要會體現在糖衣邊緣的部分。
這邊我們先開啟磁性吸附(sanp),這個功能可以讓我們在拖動頂點的時候,對拖動的方式進行輔助。這邊除了點亮磁性吸附(sanp)的磁鐵按鈕以外,右邊的吸附模式要記得選擇面模式,並且開啟「project indivisual element」這個選項。
這麼一來我們就可以沿著鄰近物件(也就是Torus)的面去拖動糖衣上面的vertex
這邊的操作細節可以看ep4 的4:30
調整完這部分的細節之後先「Apply」糖衣的Subdivision修改器,然後再重新賦予另外一個Subdivision修改器
選定Solidify修改器後按下Ctrl+A也可以直接「Apply」
這邊我們也可以先選擇某幾個邊緣上的點,再搭配Extrude(按鍵E)這個功能,用來拉出來比較誇張的滴落形狀
這邊的細節操作可以看ep4的10:15
接著我們要來做出甜甜圈中間的凹陷處,首先選出Torus上面位於垂直方向偏中間的點,然後按下(Alt+滑鼠左鍵)選取一整圈的頂點,接著使用Scale(s)來把整圈頂點做縮放以營造出甜甜圈中間的凹陷。
如果按一下沒有選到水平中線,那就多按幾下。
這時因為Torus的中間被我們往內收縮了,所以糖衣跟Torus中間出現了一部分的空洞。
在這個情況下,我們可以利用Shrink wrap修改器。Shrink wrap修改器可以藉由選擇一個對象(Target),來讓被覆加上這個修改器的物體「貼合」這個對象。
附加上Shrink wrap修改器之後記得要把Shrink wrap修改器的順序提高到第一順位。
這邊的細節操作可以看ep4的15:36
首先在開始這一步之前先把糖衣和Torus的所有modifier(修改器)都「Apply」掉。
然後再點擊上方的Sculpting,就會進入雕刻模式。
雕刻模式其實就像是小畫家,他在左側會有多種的雕刻筆刷可以使用(這部分也可以搭配有感壓機能的繪圖板來操作,效果會比用滑鼠來的更好)
筆者比較常使用的工具有:
除此之外,這些筆刷都可以透過按下f
/shift+f
來調整筆刷大小/硬度。
這邊其實就是按照個人的感覺去選擇工具來使用就好,沒有什麼特別的步驟。
這邊的操作細節可以看ep5
在開始渲染之前,第一步我們先來把攝影機調整到適當的位置。
當然在這邊我們可以直接手動調整攝影機的位置~ 但Blender guru這邊有提供一個比較簡單的方法,也就是「Camera to View」。
首先我們先按Ctrl+alt+Numpad0
,這樣就可以直接把攝影機移動到你當前檢視景物的視角,接著再在右邊的選單裡面勾選「Camera to View」,這樣你就可以把操作視角移動的機能鎖定在當前的View裡面。
接下來,我們要來講講blender
提供的兩種渲染引擎: 「eevee」和「cycles」。
首先這兩種引擎都像我們平常在three.js
裡面做的事一樣,透過計算物體模型的面法向量、位置、景深、質地、顏色,...etc. 來運算出來在某個視角下物體所呈現的樣子。
但是它們的差異在於「處理光反射的邏輯」
其實我不確定「eevee」這個詞是不是來源於寶可夢的依布(eevee) XD。
「eevee」是一種實時渲染(realtime-render)的渲染引擎。
所謂的實時渲染(realtime-render)重視的是效率和速度,這種引擎在渲染的過程中會犧牲掉一部分的細節,用來換取更高的效能。
「eevee」適合的場景主要就是像網頁前端的模型,還有一些比較偏卡通風格渲染Style。
「cycles」則是一種靜態光線追蹤(raytracing)的渲染引擎。
所謂的「光線追蹤(raytracing)」,是一種會計算光源在物體之間反射的渲染演算方式。
近年來「光追」這個名詞應該有被越來越多人聽到。
我們可以想像,假設有一個點光源,他會向外去散發出無數條的光線,假設其中有一條光線先打到了一個紅色的物體A上,那麼那個紅色的物體A,按照自然規律,就應該會反射出一定量的紅色光到鄰近的物體B上,接著鄰近的物體B會又因為接收到射過來的紅光,再根據自己的顏色、質地去決定要反射出什麼顏色的反射光到下一個物體,或是觀察者的眼中。
而像上面這樣一連串的光線路徑投射過程的運算,就是光追的本質之一。
光線追蹤式的渲染模式追求的是細膩度與自然度,所以它拋棄了效率和速度,渲染起來相對的需要花時間。
「cycles」渲染引擎通常適用在可以預先渲染的動畫,或是高仿真的照片。
上圖左是光追的運算結果,之所以龍的部分身體有較多的紅色像素堆積,是因為光線在龍的身體各部位反射(比方說頭反射的光線接著打到前胸,前胸又反射到某處)所計算出來的結果。
另外,「cycles」在渲染的時候通常會出現下圖的狀況,也就是圖像是一點一點地被算出來,跟我們習慣的three.js
實時渲染不同~
「cycles」的渲染速度會和顯卡/CPU的等級呈正相關,所以做3D動畫渲染一般會需要較好的顯卡。
接下來我們會先focus在「eevee」的使用,畢竟我們的目標是要把甜甜圈給丟到網頁前端做渲染。
科普講解差不多先到這邊~
接下來我們在場景裡面加入一個平面(Plane)。
這邊我在開場的時候不小心忘記先把Torus往上拉一點點XD,所以這裡甜甜圈會卡在平面上。
我們把Torus和糖衣一起往上拉。
可以搭配Numpad0使用,這樣比較好確認是不是剛好拖到平面上,當然拖曳形變做完要記的「Apply」!
假如發現拖不動的話,記得檢查一下是不是你的磁性吸附(snap)還開著沒關XD
接著我先把光源拉的離甜甜圈&平面近一點,且把光源強度下修到100W,這樣就可以看到陰影出現在平面上。
再來我調整了一下相機的焦距參數。
接著我們可以看到因為現在陰影看起來破破的(Ragged),原因其實就跟我們之前在three.js
裡面發生過的一樣。也就是「shadowmap」的解析度不夠。
這邊我們可以調整eevee的shadow相關設置。
再調整一下光源的Shadow屬性。
算是好多了。
然後我們可以按下F12
來看一下渲染的結果。
有興趣的話也可以試試切換到cycles來看看渲染的狀況唷~
這邊的操作細節可以看ep6的10:25。
首先我們先分別點選糖衣、Torus、平面,然後再點選右側邊欄的材質頁籤(Material Properties),來給這三個部分填上不同的材質。
通常材質的選色會比較吃建模師對顏色的敏感度,平常可以多練習這部分的直覺。
blender的材質有各式各樣的參數,其中當然也有我們熟悉的Metallic、Roughness,這邊我們可以稍微調整一下糖衣的Metallic、Roughness,讓它變得比較能夠反射光源。
這邊我們再介紹一個有趣的屬性: Subsurface,這是一個可以改變材質透光率的屬性,將這個數值提高可以帶來類似果凍的質感,這邊我們大概給個0.01
~0.02
就有明顯的不同了。
接下來我們要往下一個階段邁進了~
在這個部分Blender guru會講比較多關於Cycles渲染的細節,大部分我是略過了,如果有興趣的話可以從ep6的15:51看起。
大部分的甜甜圈,在中間的凹槽,顏色是比較白的。
但在我們的甜甜圈上面卻沒有這一圈白色的痕跡,而且本體顏色看起來也有點太乾淨。所以我們這邊其實需要一種方法來實作比較寫實的甜甜圈。
首先我們先隱藏糖衣和平面(點選之後按下h),或是也可以點選該物件的眼睛圖標。
接下來我們打開Shading這個功能頁面。
視角的縮放可能會需要調整一下。
Shading功能頁面其實就是讓使用者可以用節點式的操作方法,來自定義一個shader材質的Feature。
其實這個功能就類似於three.js
的ShaderMaterial
。
但很可惜我們在這次的賽程沒有時間可以講到ShaderMaterial
,所謂的ShaderMaterial
是一種運用Shader
腳本,直接生成材質貼圖的方法,它的用法非常廣泛,裡面牽涉到的Know-how也非常的複雜。
如果鐵人賽可以開一個組別是能夠自由決定能夠寫幾天的就好了QQ,30天For three.js真的略短。
接著我們先來看看Shading功能頁面都有些甚麼東西。
我們接著會在這邊透過實作出由噪聲所生成的材質貼圖。
首先這邊先介紹一下node-editor的使用方式。
在node-editor中,我們可以看到很多的面板,這些面板也就是我們所謂的節點。
節點和節點之間的連線,可以透過滑鼠左鍵去點擊=>拖拉=>連接。
按下Ctrl + 滑鼠右鍵拖曳,就可以切斷路徑上的節點連線。
按下滑鼠中鍵,可以平移拖曳畫面
按下shift+A,可以決定要加入什麼樣的節點。
這些連線和節點會決定最後輸出的紋理結果。
筆者個人認為節點式渲染的原理應該是有點類似函數的Input/Output,把函數的Output傳給某個函數作為Input的概念,不過目前還沒有驗證過這個推論是不是正確的。
在這邊筆者是採用上述的節點連結還有參數,即可生成如下的紋理。
這邊節點的運用建議可以多看看ep7影片中的介紹,不過筆者自己認為這種節點式的運算方法如果要達到可以自由運用,可能要經過多次練習,並且要熟悉每個節點的使用情境。
我們其實可以發現,在前面的node-editor中我們做到的,其實有點類似three.js
中Height map、color、Normal map等多種屬性疊加起來的綜合結果。
而在three.js
中,我們平常還可以加上map
這一個屬性,也就是作為基底的紋理。
這邊我們要展示的就是要如何使用blender來生成基底紋理。
首先我們得要先把剛剛的Shading功能頁面中的color ramp這個節點刪除。並新增一個image texture的節點。
並點擊image texture上面的New按鈕,新增一張512*512大小,底色與原本Torus底色相近的的基底紋理,並取名為base map。
接著紋理的預覽畫面應該會變成像這樣。
接著再切換到 Texture Paint這個功能頁面。
Texture Paint顧名思義就是紋理繪製,它除了可以繪製基底紋理以外,當然也是可以繪製例如Metallic map、Height map等其他類型的紋理。
只要你能畫得出來的話。
這邊我們可以直接把白色的區塊塗在模型中間凹槽的地方。
如果發現突然不能直接在模型上塗色,切回去shading再切回來texture painting就可以了。
接著畫完要記得存檔。
點擊畫面左上角有寫著一個小"image"的地方,並選擇"save as",blender會直接以你剛剛在image-texture節點上命名的名字來為這個材質命名。
接著我們再回到shading功能面板,會發現紋理預覽上面已經出現我們剛剛繪製的基底紋理了~
這邊我們再稍微調整一下node editor的參數,並加入一個「mixRGB」的節點,然後把這個節點上提供的混和模式選項改為Overlay。
最後回到modeling功能頁面,解除糖衣和平面的隱藏~就得到了下圖的結果
有沒有變得越來越像一回事了XD~?
這邊的詳細操作可以看 ep8
接著我們要為這糖衣的部分撒上糖粒。
要使用Geometry nodes這個功能。
Geometry nodes 顧名思義也是一種節點編輯器,但是他和我們剛剛使用的shading功能面板不同,是可以直接生成Geometry的。
這邊首先先打開Geometry nodes面板,然後選擇糖衣,接著按一下下圖中紅圈的NEW
所謂的Geometry nodes,其實基本上算是一種modifier(修改器),我們透過這種modifier可以給原有的Geometry生成新的內容,而且這些內容可以透過組合不同的節點來決定。
通常Geometry nodes會被應用在需要產生具有規律的群體物件上(ex:大樓群、課桌椅),又或者是透過隨機seed來產生隨機分布的Geometry(有點像粒子系統的概念)。
這邊我們透過剛剛的操作,已經為糖衣的部分掛上了一個Geometry nodes的修改器。
接下來的部份其實強烈建議要看過Blender guru本人的操作,在ep9
接著我們給節點編輯器加上
Join geometry
Distribute Points on Face
instance on points
這三個節點,
然後建立一個Cylinder(圓柱狀)的物體(記得寬高要先設定好),然後把這個Cylinder,從物件列表拖曳進去節點編輯器。
我們接著把拖進來的Cylinder節點,和其餘三個節點像下圖一樣去做連結。
接著就會發現在糖衣的上面出現了兩根一樣的Cylinder。
其實做到這邊應該差不多可以看懂接下來大概要幹甚麼了。
接下來我們點選剛剛的Cylinder物件(不是節點哦~),然後輸入「rx90」讓他以X軸旋轉90度,並且把Distribute Points on Face 的density屬性提高。
形變完要記得「Apply」不然Geometry nodes不會有效果。
再來我們把Distribute Points on Face 的Rotation屬性和instance on points的Rotation屬性對連。
有點那個感覺出來了~
這邊我們先調整一下原本Cylinder物件的長度,讓他沿著Y軸縮短成為原本的一半。
當然要記得「Apply」
接著先加入下面兩個節點:
然後如下串聯。
這邊的用意在於把糖衣物件的面旋轉向量做隨機化,藉此讓每顆糖粒產生不同的旋轉角度。
接下來我們按下 ctrl+tab,然後會出現權重繪製的畫面。
這邊的操作其實就跟剛剛繪製基底紋理的時候很類似,只是權重繪製的用途在於決定糖衣上面糖粒的分布狀況。
我們在繪製權重的時候,其實在被繪製的對象上都會把這個權重的mapping資料,寫入這個物體的data property中,
我們可以在這邊找到該Property。
這邊我們把它重新命名為sprinkle density。
接下來我們把group input底下的空白屬性,和Distribute Points on Face 的density連結在一起,這時候會發現group input上面出現了density這個屬性。
我們這時候就也可以在修改器的面板看到修改器上面出現了density這個屬性。
這時候我們點擊density字樣右邊的十字按鈕,就可以發現我們可以把density指向剛剛建立的權重資料(sprinkle density)。
在把density指向剛剛建立的權重資料之後,我們必須要在group input和Distribute Points on Face的density連線之間加上一個Math 修改器,然後把我們剛剛原本下在Distribute Points on Face的density值改成下在Math上,並且把Math的混和屬性改成Multipy。
這樣糖粒就會跟著權重走了。
這邊我們其實已經可以試著給cylinder上個材質,然後看看會長怎樣了。
上完材質的cylinder會繼承顏色給畫面中所有的糖粒
接下來我們稍微修飾一下cylinder的形狀。
這邊我用我們前面介紹過的XRAY模式把圓柱的其中一個圓面頂點都框起來,接著按下Ctrl+B -- 也就是blender bevel(導角)的快捷鍵,接著拖拉就可以把一個面變成具有導角的面。
我們做到這邊為止,建模的部分先告一段落了。
其實Blender guru 的ep10 有提到要如何產生隨機形狀的糖粒,不過筆者自己覺得我們只是要展示這個甜甜圈放到three.js的scene裡面,可以不用做到這麼複雜,如果有興趣的人可以自己看看ep10
在輸出之前,我們會需要先把剛剛我們做的geometry nodes的修改器「Apply」。(很重要~~~~)
但是!! geometry nodes如果直接拿去「Apply」會整個消失掉看不見(有興趣的人可以試試看,反正可以復原),這邊我們必須要在geometry nodes上面再加上最後的一個節點「Realize Instance」
這樣一來geometry nodes產生的geometry在「Apply」之後就會繼續存在了。
接著我們需要把檔案輸出成GLTF
檔。
首先我們點選File > Export> gltf2.0
然後如下設置:
原則上就是有需要的就輸出,沒有需要的取消勾選。(像是Animation這個部分,因為我們沒有做,所以這邊就全部取消)
檔案的格式則是選成把資源和gltf本體分離的格式。
輸出完接著就可以放進three.js
專案裡面,並使用gltf loader
來讀取了。
在three.js
中,就像上面說的一樣,要讀取gltf
檔案就必須靠gltfLoader
。
gltfLoader
在使用的時候基本上與textureLoader
很類似,簡單來說就是先進行實例化之後,使用.load
方法來進行讀取。
const gltfLoader = new GLTFLoader();
const pm = new Promise((res) => {
gltfLoader.load('../static/models/donuts2/donut2.gltf', (gltf) => {
res(gltf);
});
})
async function main(){
const gltf = await pm;
}
讀取的方式基本是一樣的。
gltf
的格式其實根本就是為了three.js
而設,我們在把gltf
讀取進來之後,利用console.log
檢視他,會長得像這樣。
讀取進來就會直接是個JS物件~ 是不是很方便~
在這個讀進來的gltf物件底下,一定會存在scene
這個屬性,而這個scene
其實就是我們剛剛在blender建模時的scene
~
我們再往scene
的裡面翻
可以看到scene
底下還有個children
,這個children的兩個子項其實就分別是糖衣和Torus。
我猜應該看到這邊大多數人都明白,接下來要怎麼把它放進場景了吧XD?
要把gltf
放進場景,最快的方法其實就是直接把gltf.scene
丟進去three.js
的scene
裡面。
scene.add(gltf.scene)
通常除非我們是需要gltf內部的特定內容(比方說假設我只需要糖衣的部分),不然通常都是直接把整個scene
載入。
這邊我們還是一樣選用我們的「three.js boilerplate」來操作。
gltf
檔案,放到./static
資料夾前面的初始化和安裝流程我們就先跳過了
這邊之所以要把gltf
檔案放在./static
資料夾,是因為我們不希望webpack
會跑去解析這個檔案。
./src/ts/resource/
底下建立一個./model.ts
建立完./model.ts
之後,我們在裡面寫入要載入的資料。
interface Source {
name: string,
type: string,
paths?: string[],
path?: string
}
export const modelSources: Source[] = [
{
name: 'donutsModel',
type: 'gltfModel',
path: 'static/donuts/donuts.gltf'
}
]
./src/ts/resource/index.ts
這邊我主要是補上了用來取得gltf
的promise
包裹函數
const getGltf = (source: any) => {
const prm: Promise<SourceObj> = new Promise((res, rej) => {
gltfLoader.load(
source.paths,
(model) => {
res({
name: source.name,
content: model
});
},
null,
rej
);
});
return prm;
}
並且在getResources
這個函數裡面,補上取得./src/ts/resource/model.ts
內部資源的部分:
const sources = [...textureSources, ...modelSources];
for (let source of sources) {
switch (source.type) {
case 'cubeTexture': promiseArr.push(getCubeTexture(source));
break;
case 'texture': promiseArr.push(getTexture(source));
break;
// 加上了這個case
case 'gltfModel': promiseArr.push(getGltf(source));
break;
}
}
./src/ts/mesh/donuts.ts
這邊我們新增甜甜圈專用的ts
檔案。
import { Clock, Object3D } from "three";
import { Base } from "../class/base";
export class Donuts {
scene: Object3D;
constructor(private base: Base) {
this.setModel()
}
setModel() {
this.scene = this.base.resources.donutsModel.scene as Object3D;
this.scene.scale.set(10, 10, 10)
this.base.scene.add(this.scene);
this.scene.scale.set(50, 50, 50)
this.scene.position.set(2, 0, 0)
this.scene.rotation.x = Math.PI * 0.3
this.scene.rotation.z = Math.PI * 0.15
}
update(clock: Clock) {
this.scene.rotation.y = clock.getElapsedTime()
}
}
./src/ts/class/playground.ts
這邊就沒啥特別的,就是實例化Donuts
,並且發動Donuts
的update
方法。
import { Env } from "./env";
import { Base } from "./base";
import { Clock } from "three";
import { Donuts } from '../mesh/index'
export class Playground {
env: Env;
donuts: Donuts;
private ready = false;
constructor(private base: Base) {
this.init();
}
init() {
this.base.getResources().then(() => {
this.env = new Env(this.base);
this.donuts = new Donuts(this.base);
this.ready = true;
})
}
update(clock: Clock) {
if (this.ready) {
this.env.update(clock);
this.donuts.update(clock);
}
}
}
到這邊其實畫面已經有東西出來了~
接著就是調整環境光照,renderer輸出色彩參數,然後補上標題,背景色~
其實3D建模的打光是一個學問。
但筆者小弟我感覺自己應該還不夠格談論這門學問QQ
不過依我自己的習慣,我自己通常會放置一盞以上的點光源,加上環境光。
目標是希望可以凸顯物體的左右兩側,並給整體帶來一定的光照量。
這邊我們打上一盞環境光、一盞點光源,和一盞平行光。
import { Base } from './base';
import { AmbientLight, Clock, DirectionalLight, PointLight } from 'three';
export class Env {
ambientLight: AmbientLight;
pointLight: PointLight;
directionalLight: DirectionalLight;
constructor(private base: Base) {
this.setLights();
}
setLights() {
this.setAmbientLight();
this.setPointLight();
this.setDirectionalLight();
}
//點光源
setPointLight() {
this.pointLight = new PointLight(0x2b41a1, 3.5);
this.pointLight.position.set(5, -2, -1);
this.base.scene.add(this.pointLight)
}
// 平行光
setDirectionalLight() {
this.directionalLight = new DirectionalLight(0xfae598, 4);
this.directionalLight.position.set(-3, 2, 2);
this.base.scene.add(this.directionalLight);
}
//環境光
setAmbientLight() {
this.ambientLight = new AmbientLight(0xfae598, 0.6);
this.base.scene.add(this.ambientLight)
}
update(clock: Clock) {
}
}
燈燈燈燈~
最後講評一下,這邊前端渲染的結果跟在blender
裡面呈現的不同,通常會是下面幾個原因:
subSurface
,這個gltf
似乎是不支援的。shading
產生的紋理好像不見了: 這其實是因為blender
沒有辦法把shading
產生的紋理做打包,這個其實有方法可以解決,就是改成用cycles
去做Baking,把shading
產生的紋理烘焙成靜態貼圖。不過
shading
這部分筆者小弟我老實說其實是因為時間不夠所以沒有處理QQ。
最後這邊附上:
Repo地址: https://github.com/mizok/donuts
Github Page: https://mizok.github.io/donuts/
這次參賽的感想一句話總結就是~
「想做的事果然還是太多了,30天根本不夠用」
如果鐵人賽有40天就好了。。。
不過儘管如此,我還是很努力地爬完了自己預計的全程。
雖然說覺得自己至少也算是有運動家精神,But...
想到這邊總覺得心情很複雜 = =,但我說不出來為什麼,哈哈。